/*
 * steering.h
 *
 *  Created on: 01.01.2012
 *      Author: ignacio
 */

#ifndef STEERING_H_
#define STEERING_H_

#include "MovementBehaviour.h"
#include "path.h"

#include <iostream>
using std::cout;
using std::endl;



namespace neurignacio{

	enum SteeringBehaviour_ID{ STEERING_NONE=10, STEERING_SEEK, STEERING_PURSUE,
								STEERING_FLEE, STEERING_WANDER, STEERING_ARRIVE,
								STEERING_FACE, STEERING_EVADE, STEERING_ALIGN,
								STEERING_LOOKWHEREYOUREGOING, STEERING_SEPARATION,
								STEERING_ATTRACTION, STEERING_COLLISIONAVOIDANCE,
								STEERING_FOLLOWPATH};
//	string MovementBehaviour_ID_Description(const SteeringBehaviour_ID& movement);
	/**
	 * The steering behaviour is the base class for all dynamic
	 * steering behaviours.
	 */
	class SteeringBehaviour : public MovementBehaviour
	{
    protected:
    	virtual void init() {MovementBehaviour_ID=STEERING_NONE; MovementBehaviour_Description="STEERING_NONE";}

	public:
		SteeringBehaviour() : MovementBehaviour() {init();}
		SteeringBehaviour(Muon* follower) : MovementBehaviour(follower) {init();}
	};

	// Movement Behaviour is unchanged (no behaviour)
	class Idle : public SteeringBehaviour
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_NONE; MovementBehaviour_Description="Idle";}
	public:
		Idle() : SteeringBehaviour() {init();}
		Idle(Muon* follower) : SteeringBehaviour(follower) {init();}
		void getSteering(SteeringOutput& output) {} // Empty Behaviour
	};



	class Seek : public SteeringBehaviour, public TargetPosition
	{
    protected:
    	virtual void init() {MovementBehaviour_ID=STEERING_SEEK; MovementBehaviour_Description="STEERING_SEEK";}

	public:
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		Seek() :  SteeringBehaviour(), TargetPosition() {init();};
		Seek(Muon* follower, Vector3* point) : SteeringBehaviour(follower), TargetPosition(point){init();}
		Seek(Muon* follower, Location* followed) : SteeringBehaviour(follower), TargetPosition(followed) {init();}

		void getSteering(SteeringOutput& output);
	};

	/**
	 * The seek steering behaviour takes a target and aims in the
	 * opposite direction with maximum acceleration.
	 */
	class Flee : public Seek
	{
    protected:
    	virtual void init() {MovementBehaviour_ID=STEERING_FLEE; MovementBehaviour_Description="STEERING_FLEE";}
	public:
		Flee() {init();};
		Flee(Muon* follower, Vector3* point) : Seek(follower, point){init();}
		Flee(Muon* follower, Location* followed) : Seek(follower, followed) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
    	void getSteering(SteeringOutput& output);
	};


	class Arrive: public SteeringBehaviour, public TargetPosition, public TimeToTarget, public ApproachLinearDistance
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_ARRIVE; MovementBehaviour_Description="STEERING_ARRIVE";}

	public:
		Arrive() : SteeringBehaviour(), TargetPosition(), TimeToTarget(), ApproachLinearDistance() {init();}
		Arrive(Muon* follower, Vector3* point, real timeToTarget=0, real slowRadius=0, real targetRadius=0)
			: SteeringBehaviour(follower), TargetPosition(point), TimeToTarget(timeToTarget), ApproachLinearDistance(slowRadius, targetRadius) {init();}
		Arrive(Muon* follower, Location* followed, real timeToTarget=0, real slowRadius=0, real targetRadius=0)
			: SteeringBehaviour(follower), TargetPosition(followed), TimeToTarget(timeToTarget), ApproachLinearDistance(slowRadius, targetRadius) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
    	void getSteering(SteeringOutput& output);
	};

	class Align : public SteeringBehaviour, public TargetOrientation, public TimeToTarget, public ApproachAngularDistance
	{
	protected:
			virtual void init() {MovementBehaviour_ID=STEERING_ALIGN; MovementBehaviour_Description="STEERING_ALIGN";}

	public:
		Align() : SteeringBehaviour(), TargetOrientation(), TimeToTarget(), ApproachAngularDistance() {init();}
		Align(Muon* follower, Location* followed, real timeToTarget=0, angle slowRadius=0, angle targetRadius=0)
			: SteeringBehaviour(follower), TargetOrientation(followed), TimeToTarget(timeToTarget), ApproachAngularDistance(slowRadius, targetRadius) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
    	void getSteering(SteeringOutput& output);
	};

	class Face : public Align, public TargetPosition
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_FACE; MovementBehaviour_Description="STEERING_FACE";}

	public:
		Face(): Align(), TargetPosition() {init();}
		Face(Muon* follower, Location* followed)
			: Align(follower, NULL), TargetPosition(followed) {init();}
		void setTarget(Location* followed) {Align::setTarget(followed); TargetPosition::setTarget(followed);}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class LookWhereYoureGoing : public Align
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_LOOKWHEREYOUREGOING; MovementBehaviour_Description="STEERING_LOOKWHEREYOUREGOING";}

	public:
		LookWhereYoureGoing(): Align() {init();}
		LookWhereYoureGoing(Muon* follower) : Align(follower, NULL) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};
	typedef LookWhereYoureGoing Look; // Alias for LookWhereYoureGoing

	class Pursue : public Seek, public TargetVelocity, public VirtualTargetPosition, public MaxPrediction
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_PURSUE; MovementBehaviour_Description="STEERING_PURSUE";}
	public:
		Pursue() : Seek(), TargetVelocity(), VirtualTargetPosition(), MaxPrediction() {init();}
		Pursue(Muon* follower, Lepton* followed, real maxPrediction) : Seek(follower, NULL_LOCATION_POINTER), TargetVelocity(followed),VirtualTargetPosition(followed), MaxPrediction(maxPrediction) {init();}
		void setTarget(Lepton* followed) {Seek::setTarget(followed); TargetVelocity::setTarget(followed);VirtualTargetPosition::setVirtualTarget(followed);}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class Evade : public Flee, public TargetVelocity, public VirtualTargetPosition, public MaxPrediction
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_EVADE; MovementBehaviour_Description="STEERING_EVADE";}
	public:
		Evade(): Flee(), TargetVelocity(), VirtualTargetPosition(), MaxPrediction() {init();}
		Evade(Muon* follower, Lepton* followed, real maxPrediction) : Flee(follower, NULL_LOCATION_POINTER), TargetVelocity(followed),VirtualTargetPosition(followed), MaxPrediction(maxPrediction) {init();}
		void setTarget(Lepton* followed) {Flee::setTarget(followed); TargetVelocity::setTarget(followed);VirtualTargetPosition::setVirtualTarget(followed);}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class VelocityMatch : public SteeringBehaviour, public TargetVelocity, public TimeToTarget
	{
	protected:
			virtual void init() {MovementBehaviour_ID=STEERING_ALIGN; MovementBehaviour_Description="STEERING_ALIGN";}

	public:
		VelocityMatch(): SteeringBehaviour(), TargetVelocity(), TimeToTarget() {init();}
		VelocityMatch(Muon* follower, Lepton* followed, real timeToTarget) : SteeringBehaviour(follower), TargetVelocity(followed), TimeToTarget(timeToTarget) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class Wander : public Face
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_WANDER; MovementBehaviour_Description="STEERING_WANDER";}
	public:
		real wanderOffset;
		real wanderRadius;
		real wanderRate;
	public:
		Wander() : Face(), wanderOffset(), wanderRadius(), wanderRate() {init();}
		Wander(Muon* follower) : Face(follower, follower), wanderOffset(), wanderRadius(), wanderRate() {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class Separation : public SteeringBehaviour, public TargetPosition
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_SEPARATION; MovementBehaviour_Description="STEERING_SEPARATION";}
	public:
		real thresholdDistance; // Distance between character and target in which behaviour will be active
		real decayCoefficient; // the strength of the movement is related to the distance from the target
	public:
		Separation() : SteeringBehaviour(), TargetPosition(), thresholdDistance(0), decayCoefficient(1) {init();}
		Separation(Muon* follower, Vector3* point, real thresholdDistance, real decayCoefficient) : SteeringBehaviour(follower), TargetPosition(point), thresholdDistance(thresholdDistance), decayCoefficient(decayCoefficient){init();}
		Separation(Muon* follower, Location* followed, real thresholdDistance, real decayCoefficient) : SteeringBehaviour(follower), TargetPosition(followed), thresholdDistance(thresholdDistance), decayCoefficient(decayCoefficient) {init();}
		void setThresholdDistance(real th) {thresholdDistance=th;}
		void setDecayCoefficient(real coef) {decayCoefficient=coef;}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class Attraction : public Separation
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_ATTRACTION; MovementBehaviour_Description="STEERING_ATTRACTION";}
	public:
		Attraction() : Separation() {init();}
		Attraction(Muon* follower, Vector3* point, real thresholdDistance, real decayCoefficient) : Separation(follower, point, thresholdDistance, decayCoefficient) {init();}
		Attraction(Muon* follower, Location* followed, real thresholdDistance, real decayCoefficient) : Separation(follower, followed, thresholdDistance, decayCoefficient) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class CollisionAvoidance : public SteeringBehaviour, public TargetPosition,  public TargetVelocity, public MaxPrediction
	{
	protected:
		virtual void init() {MovementBehaviour_ID=STEERING_COLLISIONAVOIDANCE; MovementBehaviour_Description="STEERING_COLLISIONAVOIDANCE";}
	public:
		// Distance between character and target that it will be considered as collision (i.e. character sizes)
		// We don't assume that character and target have the same radius (opposite to book)
		// therefore collisionRadius = character radius + target radius
		real collisionRadius;
	public:
		CollisionAvoidance() : SteeringBehaviour(), TargetPosition(), TargetVelocity(), MaxPrediction(), collisionRadius(0){init();}
		CollisionAvoidance(Muon* follower, Lepton* followed, real collisionRadius, real maxPrediction) : SteeringBehaviour(follower), TargetPosition(followed), TargetVelocity(followed), MaxPrediction(maxPrediction), collisionRadius(collisionRadius) {init();}
		void setCollisionRadius(real radius) {collisionRadius=radius;}
		void setTarget(Lepton* followed) {TargetPosition::setTarget(followed); TargetVelocity::setTarget(followed);}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
		void getSteering(SteeringOutput& output);
	};

	class TargetPath
	{
	protected:
    	/**
		 * Holds the path to follow
		 */
		VectorPath* targetPath;
	public:
		TargetPath() {}
		TargetPath(VectorPath* path) : targetPath(path) {}
		void setTarget(VectorPath* path) {targetPath=path;}
		string description() {return "TargetPath";}
	};

	class SeekPath : public Seek, public TargetPath, public MaxPrediction
	{
    protected:
    	virtual void init() {MovementBehaviour_ID=STEERING_FOLLOWPATH; MovementBehaviour_Description="STEERING_FOLLOWPATH";}
    public:
    	/*
    	 * Holds the distance along the path to generate the target.
    	 * Can be negative if the character is to move along the reverse direction.
    	 */
    	real pathOffset;
	public:
    	SeekPath() : Seek(), TargetPath(), MaxPrediction(), pathOffset(0) {init();};
    	SeekPath(Muon* follower, VectorPath* path, real pathOffset, real time) : Seek(follower, follower), TargetPath(path), pathOffset(pathOffset), MaxPrediction(time) {init();}
		/**
		 * Works out the desired steering and writes it into the given
		 * steering output structure.
		 */
    	void getSteering(SteeringOutput& output);
	};
	typedef SeekPath FollowPath;

} // end namespace neurignacio

#endif /* STEERING_H_ */
